package fr.gaulupeau.apps.Poche.tts.source;

import android.os.Build;
import android.os.Handler;
import android.util.Log;

import androidx.annotation.RequiresApi;

import java.util.ArrayList;
import java.util.List;

import fr.gaulupeau.apps.Poche.tts.TtsConverter;
import fr.gaulupeau.apps.Poche.tts.item.GenericItem;

public abstract class AbstractTextInterface<HOST extends TtsHost> implements TextInterface {

    private static final String TAG = AbstractTextInterface.class.getSimpleName();
    //protected T item;
    protected List<GenericItem> textList=new ArrayList<>();

    protected int current =-1;

    protected TtsConverter ttsConverter;

    protected HOST ttsHost;

    protected final Handler handler = new Handler();

    protected Integer storedScrollPosition;

    protected Runnable readFinishedCallback;

    protected Runnable parsingFinishedCallback;

    public AbstractTextInterface(TtsConverter ttsConverter, HOST ttsHost){
        this.ttsConverter = ttsConverter;
        this.ttsHost=ttsHost;
    }

    public void setTtsHost(HOST ttsHost) {
        this.ttsHost = ttsHost;
    }

    public void setReadFinishedCallback(Runnable readFinishedCallback) {
        this.readFinishedCallback = readFinishedCallback;
    }

    @Override
    public synchronized void restoreFromStart() {
        Log.d(TAG, "restoreFromStart -> current = 0");
        current = 0;
    }

    @Override
    public int getCurrentIndex() {
        return current;
    }

    @Override
    public GenericItem getItem(int index) {
        this.current =index;
        return textList.get(index);
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public void addItem(GenericItem item) {
        GenericItem prevItem = !textList.isEmpty() ? textList.get(textList.size() - 1) : null;
        item.timePosition = ttsConverter.approximateDuration(item)+ (prevItem != null ? prevItem.timePosition : 0);
        textList.add(item);
    }

    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    @Override
    public CharSequence getConvertedText(int index) {

        if(index < 0 || index >= textList.size()) return null;

        GenericItem item = textList.get(index);

        if (item == null) return null;

        CharSequence result = ttsConverter.convert(item);

        if (result == null) {
            // always return something so `onSpeakDone` is called the same amount of times
            // as `tts.speak()`
            result = "";
        }

        return result;
    }


    @Override
    public long getTimePositionInItem(int currentIndex, int indexInItem) {
        return ttsConverter.getTimePositionInItem(textList.get(currentIndex),indexInItem);
    }

    /**
     * Go to the next text item.
     *
     * @return true if current item changed (not already the end).
     */
    @Override
    public synchronized boolean next() {
        //Log.d(TAG, "next, current=" + current);
        boolean result;
        if (current < textList.size() - 1) {
            current++;
            result = true;
        } else {
            handler.post(readFinishedCallback);
            result = false;
        }
        ensureTextRangeVisibleOnScreen(false);
        return result;
    }

    @Override
    public synchronized boolean rewind(long desiredTimeToRewind,
                                       int currentIndex, long progressInCurrentItem) {
        int startIndex = current;
        if (currentIndex != startIndex) progressInCurrentItem = 0;

/*
        if (startIndex == 0 && progressInCurrentItem < desiredTimeToRewind / 10) {
            Log.d(TAG, "rewind() no time to rewind");
            return false;
        }
*/

        int index = startIndex;
        long timeToRewind = desiredTimeToRewind - progressInCurrentItem;

        while (timeToRewind > 0 && index > 0) {
            int newIndex = index - 1;
            long newTimeToRewind = timeToRewind - itemDuration(newIndex);
            long alreadyRewound = desiredTimeToRewind - timeToRewind;

            if (newTimeToRewind > 0 || alreadyRewound < desiredTimeToRewind / 2) {
                index = newIndex;
                timeToRewind = newTimeToRewind;
            } else {
                break;
            }
        }

        Log.d(TAG, "rewind() " + startIndex + " => " + index);
        current = index;

        ensureTextRangeVisibleOnScreen(true);

        return true;
    }

    @Override
    public synchronized boolean fastForward(long desiredTimeToSkip,
                                            int currentIndex, long progressInCurrentItem) {
        int startIndex = current;
        if (currentIndex != startIndex) progressInCurrentItem = 0;

        if (startIndex == textList.size() - 1) {
            Log.d(TAG, "fastForward() no time to skip");
            return false;
        }

        int index = startIndex + 1;
        long timeToSkip = desiredTimeToSkip - (itemDuration(startIndex) - progressInCurrentItem);

        while (timeToSkip > 0 && index < textList.size() - 1) {
            int newIndex = index + 1;
            long newTimeToSkip = timeToSkip - itemDuration(index);
            long alreadySkipped = desiredTimeToSkip - timeToSkip;

            if (newTimeToSkip > 0 || alreadySkipped < desiredTimeToSkip / 3) {
                index = newIndex;
                timeToSkip = newTimeToSkip;
            } else {
                break;
            }
        }

        Log.d(TAG, "fastForward() " + startIndex + " => " + index);
        current = index;

        ensureTextRangeVisibleOnScreen(true);

        return true;
    }

    private long itemDuration(int index) {
        return textList.get(index).timePosition
                - (index > 0 ? textList.get(index - 1).timePosition : 0);
    }

    @Override
    public synchronized long getTime() {
        long result = -1;
        if (current > 0) {
            result = textList.get(current - 1).timePosition;
        }
        return result;
    }

    @Override
    public long getTotalDuration() {
        long result = -1;
        if (textList.size() > 0) {
            result = textList.get(textList.size() - 1).timePosition;
        }
        return result;
    }

    @Override
    public boolean skipToNext() {
        return ttsHost != null && ttsHost.nextArticle();
    }

    @Override
    public boolean skipToPrevious() {
        return ttsHost != null && ttsHost.previousArticle();
    }

    @Override
    public void storeCurrent() {
        if (ttsHost != null || ttsHost.ensureTextRangeVisible())
            storedScrollPosition = ttsHost.getScrollY() ;
    }

    @Override
    public synchronized void restoreCurrent() {
        if (ttsHost == null || !ttsHost.ensureTextRangeVisible()) return;

        int result = Math.min(current, textList.size() - 1);
        if(result<0) return;

        if (storedScrollPosition != null) {
            int position = storedScrollPosition;
            storedScrollPosition = null;

            if (position == ttsHost.getScrollY()) {
                // no scrolling has been done since pause, don't restore anything
                return;
            }
        }

        float currentTop = ttsHost.getScrollY();
        float currentBottom = currentTop + ttsHost.getViewHeight();

        GenericItem textItem = textList.get(result);
        if (textItem.bottom <= currentTop || textItem.top >= currentBottom) {
            // current not displayed on screen, switch to the first text visible:
            result = textList.size() - 1;
            for (int i = 0; i < textList.size(); i++) {
                if (textList.get(i).top > currentTop) {
                    result = i;
                    break;
                }
            }
        }
        current = result;
        Log.d(TAG, "restoreCurrent -> current = " + current);
    }


    protected void ensureTextRangeVisibleOnScreen(boolean canMoveBackward) {
        if (ttsHost == null || !ttsHost.ensureTextRangeVisible()) return;

        GenericItem item = textList.get(current);

        if (item.bottom > ttsHost.getScrollY() + ttsHost.getViewHeight()
                || canMoveBackward && item.top < ttsHost.getScrollY()) {
            // TODO: check: call directly?
            handler.post(() -> ttsHost.scrollTo((int) item.top));
        }
    }
}
